﻿namespace ProjectName.ScheduledJobs
{
    using System;
    using System.Configuration;
    using System.Data.SqlClient;
    using System.Linq;
    using System.Threading;
    using System.Web;
    using EPiServer.DataAbstraction;

    public static class ScheduledJobMaintenance
    {
        private static DateTime? _lastRunTime;
        private static int _maximumJobRunTimeInMinutes = 60;
        private static int _checkTimeInMinutes = 11;
        private static bool _isRunning;
        private static readonly object _locker = new object();

        public static int MaximumJobRunTimeInMinutes
        {
            get { return _maximumJobRunTimeInMinutes; }
            set { _maximumJobRunTimeInMinutes = value; }
        }

        public static int CheckTimeInMinutes
        {
            get { return _checkTimeInMinutes; }
            set { _checkTimeInMinutes = value; }
        }

        public static void CheckScheduledJobs()
        {
            if (_isRunning || (_lastRunTime.HasValue && (DateTime.Now - _lastRunTime.Value).TotalMinutes <= CheckTimeInMinutes) || HttpContext.Current.Request.Url.AbsoluteUri.IndexOf(".aspx", StringComparison.OrdinalIgnoreCase) == -1)
                return;

            ThreadPool.QueueUserWorkItem(CheckJobs);
        }

        private static void CheckJobs(object parameters)
        {
            if (!CanRunCheck())
                return;

            try
            {
                foreach (ScheduledJob scheduledJob in ScheduledJob.List().Where(job => job.IsEnabled
                    && job.IntervalType != ScheduledIntervalType.None
                    && ((job.IsRunning && HasJobBeenRunningTooLong(job)) || JobHasNotRun(job))))
                {
                    bool saveJob = false;

                    if (scheduledJob.NextExecution != DateTime.MinValue)
                    {
                        switch (scheduledJob.IntervalType)
                        {
                            case ScheduledIntervalType.Months:
                                SetNextExecutionTimeInMonths(scheduledJob);
                                saveJob = true;
                                break;
                            case ScheduledIntervalType.Weeks:
                                SetNextExecutionTimeInWeeks(scheduledJob);
                                saveJob = true;
                                break;
                            case ScheduledIntervalType.Days:
                                SetNextExecutionTimeInDays(scheduledJob);
                                saveJob = true;
                                break;
                            case ScheduledIntervalType.Hours:
                                SetNextExecutionTimeInHours(scheduledJob);
                                saveJob = true;
                                break;
                            case ScheduledIntervalType.Minutes:
                                SetNextExecutionTimeInMinutes(scheduledJob);
                                saveJob = true;
                                break;
                            case ScheduledIntervalType.Years:
                            case ScheduledIntervalType.Seconds:
                            case ScheduledIntervalType.None:
                                break;
                        }
                    }

                    if (saveJob)
                        SaveJob(scheduledJob);
                }
            }
            catch (Exception ex)
            {
                // log error
            }

            _lastRunTime = DateTime.Now;
            _isRunning = false;
        }

        private static bool CanRunCheck()
        {
            lock (_locker)
            {
                if (_isRunning)
                    return false;

                _isRunning = true;
            }

            return true;
        }

        private static bool HasJobBeenRunningTooLong(ScheduledJob scheduledJob)
        {
            return (DateTime.Now - scheduledJob.LastExecution).TotalMinutes > MaximumJobRunTimeInMinutes;
        }

        private static bool JobHasNotRun(ScheduledJob scheduledJob)
        {
            return scheduledJob.NextExecution.AddMinutes(MaximumJobRunTimeInMinutes) < DateTime.Now;
        }

        private static void SetNextExecutionTimeInMonths(ScheduledJob scheduledJob)
        {
            DateTime now = DateTime.Now;
            DateTime nextExecution = scheduledJob.NextExecution;

            while (nextExecution < now)
                nextExecution = nextExecution.AddMonths(scheduledJob.IntervalLength);

            scheduledJob.NextExecution = nextExecution;
        }

        private static void SetNextExecutionTimeInWeeks(ScheduledJob scheduledJob)
        {
            DateTime now = DateTime.Now;
            DateTime nextExecution = scheduledJob.NextExecution;

            while (nextExecution < now)
                nextExecution = nextExecution.AddDays(7);

            scheduledJob.NextExecution = nextExecution;
        }

        private static void SetNextExecutionTimeInDays(ScheduledJob scheduledJob)
        {
            DateTime now = DateTime.Now;
            DateTime nextExecution = scheduledJob.NextExecution;

            while (nextExecution < now)
                nextExecution = nextExecution.AddDays(scheduledJob.IntervalLength);

            scheduledJob.NextExecution = nextExecution;
        }

        private static void SetNextExecutionTimeInHours(ScheduledJob scheduledJob)
        {
            DateTime now = DateTime.Now;
            DateTime nextExecution = scheduledJob.NextExecution;

            while (nextExecution < now)
                nextExecution = nextExecution.AddHours(scheduledJob.IntervalLength);

            scheduledJob.NextExecution = nextExecution;
        }

        private static void SetNextExecutionTimeInMinutes(ScheduledJob scheduledJob)
        {
            DateTime now = DateTime.Now.AddMinutes(1);
            DateTime nextExecution = scheduledJob.NextExecution;

            while (nextExecution < now)
                nextExecution = nextExecution.AddMinutes(scheduledJob.IntervalLength);

            scheduledJob.NextExecution = nextExecution;
        }

        private static void SaveJob(ScheduledJob scheduledJob)
        {
            using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["EPiServerDB"].ConnectionString))
            {
                string sql = string.Format("UPDATE [tblScheduledItem] SET [NextExec] = '{0}', [CurrentStatusMessage] = '', [LastStatus] = 0, [LastText] = '', [LastExec] = NULL, IsRunning = 0 WHERE pkID = '{1}'",
                    scheduledJob.NextExecution.AddHours(-1).ToString("yyyy-MM-dd hh:mm:ss:fff"),
                    scheduledJob.ID);

                using (SqlCommand command = new SqlCommand(sql, connection))
                {
                    connection.Open();
                    command.ExecuteNonQuery();
                    connection.Close();
                }
            }
        }
    }
}